/*
 * gf_unit.c
 *
 * Run unit tests on Galois field routines.
 */

#include <stdio.h>
#include <getopt.h>
#include <stdint.h>
#include <string.h>

#include "gf.h"

extern void     timer_start (double *t);
extern double   timer_split (const double *t);

static
void
fill_random (void *buf, int len)
{
    int     i;

    for (i = 0; i < len / sizeof (uint32_t); i++) {
        ((uint32_t *)buf)[i] = random ();
    }
    *((uint32_t *)((uint8_t *)buf + len - sizeof (random))) = random ();
}


static struct option opts[] = {
    {"unit_test",       no_argument,        NULL,       'u'},
    {"timing_test",     required_argument,  NULL,       't'},
    {"size",            required_argument,  NULL,       's'},
    {"mult",            required_argument,  NULL,       'm'},
    {"naccs",           required_argument,  NULL,       'a'},
    {"value",           required_argument,  NULL,       'v'},
    {NULL,              0,                  NULL,       0},
};

int
main (int argc, char *argv[])
{
    int         ch;
    int         result;
    int         run_unit_tests = 0;
    int         timing_tests = 0;
    int         test_size = 1024 * 1024;
    uint32_t    mult_flags = GALOIS_MULT_LOG;
    void        *buf1, *buf2, *bufd;
    int         i, j, k;
    void        *accs[32];
    uint64_t    factors[32];
    uint32_t    naccs = 0;
    uint32_t    sig;
    uint32_t    val = 55;
    double      timer, elapsed;
    galois_field_4_t    gf4;
    galois_field_8_t    gf8;
    galois_field_16_t   gf16;

    while ((ch = getopt_long (argc, argv, "ut:s:m:a:v:", opts, NULL)) != -1) {
        switch (ch) {
        case 'u':
            run_unit_tests = 1;
            break;
        case 't':
            timing_tests = strtol (optarg, NULL, 0);
            if (timing_tests != 8 && timing_tests != 16 && timing_tests != 4) {
                fprintf (stderr, "Timing test must specify field width, and %d isn't valid.\n", timing_tests);
                exit (-1);
            }
            if (timing_tests == 4) val = 5;
            break;
        case 's':
            test_size = strtol (optarg, NULL, 0);
            if (test_size < 128 || test_size > 400000000) {
                fprintf (stderr, "Test size must be between 128 and 400,000,000 bytes (not %d).\n", test_size);
                exit (-1);
            }
            break;
        case 'v':
            val = strtol (optarg, NULL, 0);
            break;
        case 'm':
            if (!strncasecmp (optarg, "log", 3)) {
                mult_flags = GALOIS_MULT_LOG;
            } else if (!strncasecmp (optarg, "half", 4)) {
                mult_flags = GALOIS_MULT_HALF_TABLE;
            } else if (!strncasecmp (optarg, "full-lazy", 9)) {
                mult_flags = (GALOIS_MULT_FULL_TABLE | GALOIS_MULT_LAZY);
            } else if (!strncasecmp (optarg, "full", 4)) {
                mult_flags = GALOIS_MULT_FULL_TABLE;
            } else if (!strncasecmp (optarg, "shift", 5)) {
                mult_flags = GALOIS_MULT_SHIFT;
            } else if (!strncasecmp (optarg, "bytwo", 5)) {
                mult_flags = GALOIS_MULT_BYTWO;
            } else {
                fprintf (stderr, "Multiplication type must be log, half, full, full-lazy, bytwo or shift (not %s)\n", optarg);
                exit (-1);
            }
            break;
        case 'a':
            naccs = strtol (optarg, NULL, 0);
            if (naccs < 1 || naccs > 32) {
                fprintf (stderr, "Must specify number of accumulators between 1 and 32 (not %d)\n", naccs);
                exit (-1);
            }
            break;
        default:
            break;
        }
    }


    if (run_unit_tests) {
        printf ("Unit testing 4-bit Galois field...");
        fflush (stdout);
        result = gf_w4_unit_test ();
        if (result > 0) {
            printf ("ERROR %d.\n", result);
            exit (-1);
        } else {
            printf ("done.\n");
        }
        printf ("Unit testing 8-bit Galois field...");
        fflush (stdout);
        result = gf_w8_unit_test ();
        if (result > 0) {
            printf ("ERROR %d.\n", result);
            exit (-1);
        } else {
            printf ("done.\n");
        }
        printf ("Unit testing 16-bit Galois field...");
        fflush (stdout);
        result = gf_w16_unit_test ();
        if (result > 0) {
            printf ("ERROR %d.\n", result);
        } else {
            printf ("done.\n");
        }
    }

    if (naccs > 0) {
        fill_random (factors, sizeof (factors));
    }
    if (timing_tests == 4) {
        if (!gf_w4_init (&gf4, mult_flags)) {
          printf("Bad params -- cannot initailize\n");
          exit(1);
        }
        buf1 = malloc (test_size + 16);
        buf2 = malloc (test_size + 16);
        bufd = malloc (test_size + 16);
        fill_random (buf1, test_size);
        fill_random (buf2, test_size);
        memset (bufd, 0, test_size);
        timer_start (&timer);
        gf4.mult_buf_const (&gf4, buf1, val, bufd, test_size);
        elapsed = timer_split (&timer);
        printf ("buf-const Elapsed: %.6f (%.2f MB/s)\n", elapsed, (double)test_size / (1e6 * elapsed));
        timer_start (&timer);
    } else if (timing_tests == 8) {
        if (!gf_w8_init (&gf8, mult_flags)) {
          printf("Bad params -- cannot initailize\n");
          exit(1);
        }
        buf1 = malloc (test_size + 16);
        buf2 = malloc (test_size + 16);
        bufd = malloc (test_size + 16);
        fill_random (buf1, test_size);
        fill_random (buf2, test_size);
        memset (bufd, 0, test_size);
        timer_start (&timer);
        gf8.mult_buf_const (&gf8, buf1, val, bufd, test_size);
        elapsed = timer_split (&timer);
        printf ("buf-const Elapsed: %.6f (%.2f MB/s)\n", elapsed, (double)test_size / (1e6 * elapsed));
        timer_start (&timer);
        gf8.mult_buf_buf (&gf8, buf1, buf2, bufd, test_size);
        elapsed = timer_split (&timer);
        printf ("buf-buf Elapsed: %.6f (%.2f MB/s)\n", elapsed, (double)test_size / (1e6 * elapsed));
        if (naccs > 0) {
            for (i = 0; i < naccs; i++) {
                accs[i] = malloc (test_size);
            }
            timer_start (&timer);
            gf8.mult_acc_buf (&gf8, buf1, (gf_val_8_t *)factors, accs, test_size, naccs);
            elapsed = timer_split (&timer);
            printf ("buf-%d acc Elapsed: %.6f (%.2f MB/s)\n", naccs, elapsed, (double)test_size / (1e6 * elapsed));
        }
        timer_start (&timer);
        gf8.mult_acc_sig (&gf8, buf1, 0x33, (gf_val_8_t *)&sig, 2, test_size);
        elapsed = timer_split (&timer);
        printf ("buf-sig Elapsed: %.6f (%.2f MB/s) sig=%08x\n", elapsed, (double)test_size / (1e6 * elapsed), sig);
    } else if (timing_tests == 16) {
        if (!gf_w16_init (&gf16, mult_flags)) {
          printf("Bad params -- cannot initailize\n");
          exit(1);
        }
        buf1 = malloc (test_size + 16);
        buf2 = malloc (test_size + 16);
        bufd = malloc (test_size + 16);
        fill_random (buf1, test_size);
        fill_random (buf2, test_size);
        memset (bufd, 0, test_size);
        timer_start (&timer);
        gf16.mult_buf_const (&gf16, buf1, 55, bufd, test_size/2);
        elapsed = timer_split (&timer);
        printf ("Elapsed: %.6f (%.2f MB/s)\n", elapsed, (double)test_size / (1e6 * elapsed));
        timer_start (&timer);
        gf16.mult_buf_buf (&gf16, buf1, buf2, bufd, test_size/2);
        elapsed = timer_split (&timer);
        printf ("Elapsed: %.6f (%.2f MB/s)\n", elapsed, (double)test_size / (1e6 * elapsed));
    }
}
